<?php /* 
==================================================
TextCMS -- simple plain/text CMS with GPL licence
Typografer
(c) 2009, 4X_Pro
Visit us online: http://textcms.ru
==================================================
*/

/** Типографирование текста. Правила см. в комментариях 
**/
function typograf_main($text) {
	if (strpos($text,'<!--NO_TYPOGRAF-->')===true) return $text; // при наличии в тексте конструкции <!--NO_TYPOGRAF--> его обработка не производится
  $key=md5(mt_rand()).'-';
	// обработка тега pre: все содержимое в нем остается как есть	
	$pre_count=preg_match_all('|(<pre>.*?</pre>)|is',$text,$pre_matches);
	for ($i=0; $i<$pre_count; $i++) $text=str_replace($pre_matches[0][$i],'<PRE|'.$i.'|'.$key.'>',$text);
	// обработка тега code: все содрежимое в нем остается как есть
	$code_count=preg_match_all('|(<code>.*?</code>)|is',$text,$code_matches);
	for ($i=0; $i<$code_count; $i++) $text=str_replace($code_matches[0][$i],'<CODE|'.$i.'|'.$key.'>',$text);
	
	$text = ' '.$text.' '; // добавление пробелов в начало и конец для корректной обработки первого и последнего предложений
	
	// переводы строки после тегов p и br (нужны, чтобы корректно обрабатывать начало строк)
	$text = preg_replace('|(<p\s*>)|i',"$1\n",$text); // вставляем перевод строки после тега <p> без параметров
	$text = preg_replace('|(<br\s*/?>)|i',"$1\n",$text); // вставляет перевод строки после тега <br /> без параметров
	$text = preg_replace('|(<p\W.*?>)|i',"$1\n",$text); // вставляем перевод строки после тега <p> без параметров
	$text = preg_replace('|(<br\W.*>)|i',"$1\n",$text); // вставляем перевод строки после тега <p> без параметров
	
	// подчистка лишних <span style="white-space: nowrap"> вокруг последовательностей, которые будут обрабатываться далее
	$text = preg_replace('|<span style="white-space: nowrap">(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?</span>|us','$1$2',$text); // защита от многократного span для IP
  $text = preg_replace('|<span style="white-space: nowrap">([\p{L}\p{N}]{1,4}(&#8243;)?-\p{L}+\p{P}?)</span>|us','$1',$text); // защита от многократного span для дефисов
  $text = preg_replace('#<span style="white-space: nowrap">(\+\d{1,3}\s*\(?\d+\)?\s*\d{1,3}(–|-)?\d{2}(–|-)?\d{2})</span>#s','$1',$text); // защита от многократного span для телефонов в международном формате
  $text = preg_replace('#<span style="white-space: nowrap">(\(\d+\)\s*\d{1,3}(–|-)?\d{2}(–|-)?\d{2})</span>#s','$1',$text); // защита от многократного span для телефонов в международном формате
  $text = preg_replace('#<span style="white-space: nowrap">(\d{1,3}(–|-)?\d{2}(–|-)?\d{2})</span>#s','$1',$text); // "простой" номер телефона (без кода города вообще)	

	// замена тегов с аттрибутами на спецпоследовательности, чтобы не портились при расстановке кавычек или дефисов
	$tag_count=preg_match_all('#(<\w+[^|>\w].*?>)#is',$text,$tag_matches);
	for ($i=0; $i<$tag_count; $i++) $text=str_replace($tag_matches[0][$i],'<TAG|'.$i.'|'.$key.'>',$text);
	
	// замена HTML-последовательностей, участвующих в дальнейших преобразованиях на реальные символы
	$text = preg_replace('|&nbsp;|',' ',$text); // символ с кодом 160 -- неразрывный пробел
	$text = preg_replace('|&mdash;|','—',$text);
	$text = preg_replace('|&ndash;|','–',$text);
	$text = preg_replace('|&quot;|','"',$text);
	$text = preg_replace('|&laquo;|','«',$text);
	$text = preg_replace('|&raquo;|','»',$text);
	$text = preg_replace('|&bdquo;|','„',$text);
	$text = preg_replace('|&ldquo;|','“',$text);

	// расстановка тире
	$text = preg_replace('|\s*--\s*|s',' — ',$text); // замена "--" на тире 
	$text = preg_replace('|\s+-\s+|s',' — ',$text); // замена " - " на тире

	//обработка цифр и математических знаков	
  $text = preg_replace('|([\p{Z}\p{S}\p{P}\s])1/2([\p{Z}\s\p{S}\p{P}])|us','$1&frac12;$2',$text); // дробь 1/2
	$text = preg_replace('|([\p{Z}\p{S}\p{P}\s])1/4([\p{Z}\s\p{S}\p{P}])|us','$1&frac14;$2',$text); // дробь 1/4
	$text = preg_replace('|([\p{Z}\p{S}\p{P}\s])3/4([\p{Z}\s\p{S}\p{P}])|us','$1&frac34;$2',$text); // дробь 3/4
	$text = preg_replace('|([\p{L}\p{Nd}])^2([\p{Z}\p{S}\p{P}])|us','$1&sup2;$2',$text); // степень квадрат
	$text = preg_replace('|([\p{L}\p{Nd}])^3([\p{Z}\p{S}\p{P}])|us','$1&sup3;$2',$text); // степень куб
	$text = preg_replace('#([\p{Z}\p{S}\s])(\p{L}|\p{Nd}+)\s*\*\s*(\p{L}|\p{Nd}+)([\p{Z}\p{S}\s])#us','$1$2&#183;$3$4',$text); //умножение точкой для одиночных цифр или букв
	$text = preg_replace('|([\p{Z}\s\p{L}\p{N}])\+-([\p{L}\p{N}])|us','$1&plusmn;$2',$text); // знак "плюс-минус"
	$text = preg_replace('|~=|s','&asymp;',$text); // знак "приблизительно равно"
	$text = preg_replace('|!=|s','&ne;',$text); // знак "не равно"
	$text = preg_replace('|<sup>o</sup>|is','&deg;',$text); // преобразование o в верхнем индексе в градус
	$text = preg_replace('|<sup>о</sup>|is','&deg;',$text); // преобразование o (кириллицей) в верхнем индексе в градус
	$text = preg_replace('|^o([\p{Z}\s\p{S}\p{P}])|is','&deg;$1',$text); // преобразование последовательности ^o в градус

// обработка отступов
  $text = preg_replace('|(\p{Nd})\s+—\s+(\p{Nd})|us','$1–$2',$text); // тире между цифрами пишется без отступов
	$text = preg_replace('|\p{Z}+—|us',' —',$text); // вставка неразрывного пробела, чтобы тире оставалось на той же строке
	$text = preg_replace('|(\P{L}\p{L}\.)\s*(\p{L}\.)\s*(\p{L})|us',' $1$2 $3',$text); // обработка инициалов: буквы имени и отчества пишутся слитно после точки и отделяютсяот фамилии неразрывным пробелом
	$text = preg_replace('|(\p{L})\p{Z}*,\s*(\p{L})|us','$1, $2',$text); // правильная постановка запятой
	$text = preg_replace('|(\p{L})\p{Z}*:\s*(\p{L})|us','$1: $2',$text); // правильная постановка двоеточия
	$text = preg_replace('|(\p{Z}[\p{L}\p{N}]+)\p{Z}*;\s*(\p{L})|us','$1; $2',$text); // правильная постановка точки с запятой
  $text = preg_replace('|([\p{L}\p{N}])\p{Z}*!\p{Z}?(\p{L})|us','$1! $2',$text); // правильная постановка восклицательного знака
  $text = preg_replace('|([\p{L}\p{N}])\p{Z}*\?\p{Z}?(\p{L})|us','$1? $2',$text); // правильная постановка вопросительного знака	
  $text = preg_replace('|([\p{L}\p{N}])\p{Z}*\.(\p{Z}\p{L})|us','$1. $2',$text); // правильная постановка точки в конце предложения
// скобки
	$text = preg_replace('|(\p{Ps})\p{Z}*(.*?)\p{Z}*(\p{Pe})|u','$1$2$3',$text); // удаление пробелов после открывающих и перед закрывающими скобками, если они парные
  $text = preg_replace('|(\p{Z}[\p{L}\p{P}]+)(\()|u','$1 $2',$text); // добавление пробела перед открывающей скобкой, если перед ней стоит знак препинания или буква
  $text = preg_replace('|(\))(\p{L})|u','$1 $2',$text); // добавление пробела после закрыающей скобки, если после нее стоит буква (но не знак препинания, т.к. знаки там вполне могут быть)

// обработка сокращений и коротких слов (предлогов, союзов и т.п.)
  $text = preg_replace('|([\p{N}\p{Z}/])м2([\p{Z}\s\p{P}])|us','$1м&sup2;$2',$text); // метры квадратные
	$text = preg_replace('|([\p{N}\p{Z}/])м3([\p{Z}\s\p{P}])|us','$1м&sup3;$2',$text); // метры кубические
	$text = preg_replace('|([\p{N}\p{Z}/])см2([\p{Z}\s\p{P}])|us','$1см&sup2;$2',$text); // сантиметры квадратные
	$text = preg_replace('|([\p{N}\p{Z}/])км2([\p{Z}\s\p{P}])|us','$1км&sup2;$2',$text); // километры квадратные

//  $text = preg_replace('|([\p{Z}\s\p{S}\p{P}]\p{N}+)\p{Z}*(\p{L}{1,3}[\p{Z}\p{P}])|us','$1 $2',$text); // обозначение после числа (годы, единицы измерения) закомментировано в 0.96b
	$text = preg_replace('|([\p{Z}\s]\p{L}\.)\p{Z}+(\p{L})|us','$1 $2',$text); // сокращение с точкой (например, г. Москва)
		$text = preg_replace('#(\p{Z})(АО|ООО|ЗАО|ОАО|ИЧП|ПБОЮЛ|ГУП|ФГУП)\p{Z}("?\p{L})#us','$1$2 $3',$text); // приклеивание сокращений типа АО/ООО к названию организации
  $text = preg_replace('#(\p{L}+)\p{Z}+((ли|же|бы|б)[\p{Z}\p{P}])#us','$1 $2',$text); // приклеивание частиц ли же бы к предыдущим	словам
	$text = preg_replace('|(\p{Z}\p{L}{1,4})\p{Z}+(\p{L}+[\p{Z}\p{P}])|us','$1 $2',$text); // приклеивание коротких слов (до 4 букв) к следующим
	
// символы разные
  $text = preg_replace('|([\p{Z}\s\p{P}])\(c\)([\p{Z}\s\p{P}])|isu','$1&copy;$2',$text); // символ copyright
	$text = preg_replace('|([\p{Z}\s\p{P}])\(tm\)([\p{Z}\s\p{P}])|isu','$1&trad;$2',$text); // символ copyright
	$text = preg_replace('|([\p{Z}\s\p{P}])\(r\)([\p{Z}\s\p{P}])|isu','$1&reg;$2',$text); // символ copyright
	$text = preg_replace('|([\p{L}\p{Nd}])\.{3,5}|u','$1&hellip;',$text); // многоточие

// обработка кавычек
  $text = preg_replace('#([\s\p{Z}\p{P}])"([\p{L}\p{N}].*?[\p{L}\p{N}\p{P}])"([\s\p{Z}\p{P}])#us','$1«$2»$3',$text); // замена парных кавычек, кавычка считается открывающей, если слева -- пробельный символ или тег перевода строки, закрывающей -- если слева непробельный символ, а справа -- не буквенный
	$text = preg_replace('#([\s\p{Z}\p{P}])"([\p{L}\p{N}].*?[\p{L}\p{N}\p{P}])"([\s\p{Z}\p{P}])#us','$1«$2»$3',$text); // операция выполняется дважды для обработки вложенных кавычек 
  $text = preg_replace('#([\s\p{Z}\p{P}]«[\p{L}\p{N}][^»]*[\s\p{Z}\p{P}])«([\p{L}\p{N}][^»]*[\p{L}\p{N}\p{P}])»»#us','$1„$2“»',$text); // замена парных кавычек, кавычка считается открывающей, если слева -- пробельный символ или тег перевода строки, закрывающей -- если слева непробельный символ, а справа -- не буквенный	
  $text = preg_replace('#([\s\p{Z}\p{P}]«[\p{L}\p{N}][^»]*[\s\p{Z}\p{P}])«([\p{L}\p{N}][^»]*[\p{L}\p{N}\p{P}])»(([\s\p{Z}\p{P}][^»]*)?»)#us','$1„$2“$3',$text); // замена парных кавычек, кавычка считается открывающей, если слева -- пробельный символ или тег перевода строки, закрывающей -- если слева непробельный символ, а справа -- не буквенный
  
	// расстановка дефиса
	$text = preg_replace('|([\p{Z}\s])([\p{L}\p{N}]{1,4}"?-\p{L}+\p{P}?)([\p{Z}\s])|us','$1<span style="white-space: nowrap">$2</span>$3',$text); // если слово перед дефисом содержит меньше 4 букв или цифр, делаем его неразрывным
  $text = preg_replace('|(\p{N})"([\p{Z}\p{S}\p{P}\s])|us','$1&#8243;$2',$text); // знак дюйма после числа (например, 19" монитор)	
  
	// обработка IP-адресов (с портами или без)
	$text = preg_replace('|(\D)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?(\D)|us','$1<span style="white-space: nowrap">$2$3</span>$4',$text); // неразрывный IP-адрес	
	
	//телефоны
	$text = preg_replace('#\+(\d{1,3})\s*\(?(\d+)\)?\s*(\d{1,3})(–|-)?(\d{2})(–|-)?(\d{2})#s','<span style="white-space: nowrap">+$1 ($2) $3–$5–$7</span>$8',$text); // обработка номеров телефонов в международном формате
	$text = preg_replace('#(\D\s)\((\d+)\)\s*(\d{1,3})(–|-)?(\d{2})(–|-)?(\d{2})(\D)#s','$1<span style="white-space: nowrap">($2) $3–$5–$7</span>$8',$text); // обработка номеров телефонов только с кодом города
	$text = preg_replace('#([^)]\s)(\d{1,3})(–|-)(\d{2})(–|-)(\d{2})(\D)#s','$1<span style="white-space: nowrap">$2–$4–$6</span>$7',$text); // "простой" номер телефона (без кода города вообще)

  // восстановление теговых последовательностей вообще
	for ($i=0; $i<$tag_count; $i++) $text=str_replace('<TAG|'.$i.'|'.$key.'>',$tag_matches[0][$i],$text);
	
	// удаление вставленных переводов строки после тегов p и br (нужны, чтобы корректно обрабатывать начало строк)
	$text = preg_replace('|(<p\s*>)\n|i','$1',$text);
	$text = preg_replace('|(<br\s*/?>)\n|i','$1',$text);
	$text = preg_replace('|(<p\W.*?>)\n|i','$1',$text);
	$text = preg_replace('|(<br\W.*>)\n|i','$1',$text);
	
	// прочая коррекция
	$text = preg_replace('| \s+|s',' ',$text); // удаление пробелов после  , т.к. их наличие приводит к потере
	$text = preg_replace('|(\p{Z})(4) X_Pro(\P{L})|su','$1$2X_Pro$3',$text); // корректировка написания 4X_Pro
	$text = preg_replace('|(\p{Z})(4) xpro(\P{L})|su','$1$2xpro$3',$text); // корректировка написания 4xpro

	$text = substr($text,1,strlen($text)-2); // обрезаем первый и последний пробел, которые добавили в начале
	// восстановление тега pre
	for ($i=0; $i<$pre_count; $i++) $text=str_replace('<PRE|'.$i.'|'.$key.'>',$pre_matches[0][$i],$text);
	// восстановление тега code
	for ($i=0; $i<$code_count; $i++) $text=str_replace('<CODE|'.$i.'|'.$key.'>',$code_matches[0][$i],$text);
	return $text;
}
